package com.dc.dbus.session.client;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;

import com.dc.dbus.session.Log;
import com.dc.dbus.session.annotation.Areq;
import com.dc.dbus.session.annotation.Mreq;
import com.dc.dbus.session.annotation.ReqType;
import com.dc.dbus.session.annotation.Router;
import com.dc.dbus.session.annotation.Sreq;
import com.dc.dbus.session.bean.FutureResult;
import com.dc.dbus.session.listener.ClientSideEventListener;
import com.dc.dbus.session.register.ServiceRegister;
import com.dc.dbus.transport.commons.ClientSideTransport;

public class Client {
	
	protected final ClientStubFatory clientStubFatory;
	
	protected final ServiceRegister serviceRegister;
	
	public Client(ServiceRegister serviceRegister, ClientStubFatory clientStubFatory) {
		super();
		this.serviceRegister = serviceRegister;
		this.clientStubFatory = clientStubFatory;
	}
	
	public Client(ServiceRegister serviceRegister, 
			ClientSideTransport clientSideTansport, ClientSideEventListener eventListener) {
		super();
		
		this.serviceRegister = serviceRegister;
		
		ClientInvokeFeedback clientInvokeFeedback = new ClientInvokeFeedback(eventListener);
		this.clientStubFatory = new DefaultClientStubFatory(clientSideTansport, clientInvokeFeedback);
		
		clientSideTansport.startClientSideReceiver(clientInvokeFeedback);
	}
	
	public <T> T createClientStub(Class<T> interfaceClass) {
		return createClientStub(interfaceClass.getName(), interfaceClass);
	}

	public <T> T createClientStub(String serviceName, Class<T> interfaceClass) {
		
		String defaultRouter = null;
		
		Annotation[] objAnnotations = interfaceClass.getAnnotations();
		for (Annotation a : objAnnotations) {
			if (a instanceof Router) {
				defaultRouter = ((Router) a).value();
				break;
			}
		}
		
		HashSet<Integer> cmdSet = new HashSet<Integer>();
		HashMap<String, MethodInfo> methodNameMapInfo = new HashMap<String, MethodInfo>();
		
		Method[] methods = interfaceClass.getMethods();
		for (Method m : methods) {
			
			Annotation[] methodAnnotations = m.getAnnotations();
			ReqType reqType = null;
			int cmd = 0, timeout = 0;
			for (Annotation a : methodAnnotations) {
				if (a instanceof Sreq) {
					reqType = ReqType.SYNC;
					cmd = ((Sreq) a).value();
					timeout = ((Sreq) a).timeout();
					break;
				} else if (a instanceof Areq) {
					reqType = ReqType.ASYNC;
					cmd = ((Areq) a).value();
					timeout = ((Areq) a).timeout();
					if (!m.getReturnType().isAssignableFrom(FutureResult.class)) {
						// 声明为异步的方法返回类型必须为FutureResult<xxx>
						throw new RuntimeException(
								"interface(" + interfaceClass.getName() + ") method(" + m.getName() 
								+ ") is decalare by Areq, the return type must be FutureResult<>");
					}
					break;
				} else if (a instanceof Mreq) {
					reqType = ReqType.MSG;
					cmd = ((Mreq) a).value();
					if (m.getReturnType() != void.class) {
						// 声明为消息的方法不能有返回参数
						throw new RuntimeException(
								"interface(" + interfaceClass.getName() + ") method(" + m.getName() 
								+ ") is decalare by Mreq, must not have return type(return type should be void)");
					}
					break;
				}
			}
			
			if (reqType == null) {
				// 代理接口的所有方法都必须声明 请求类型('Sreq' or 'Areq' or 'Mreq')
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") method(" + m.getName() + ") must have req Annotation('Sreq' or 'Areq' or 'Mreq')");
			}
			
			if (!cmdSet.add(cmd)) {
				// 代理接口中有不同方法使用相同的cmd
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") method(" + m.getName() + ")'s cmd(" + cmd + ") is same with other method");
			}
			
			Class<?>[] paramterTypes = m.getParameterTypes();
			Annotation[][] methodParamsAnnotations = m.getParameterAnnotations();
			int paramRouterIndex = -1;
			findRouter: for (int i=0; i<paramterTypes.length; i++) {
				for (Annotation a : methodParamsAnnotations[i]) {
					if (a instanceof Router) {
						if (!paramterTypes[i].equals(String.class)) {
							// 方法中含有注解Router的参数必须是String类型
							throw new RuntimeException(
									"interface(" + interfaceClass.getName() + ") method(" + m.getName() 
									+ ")', the param(have Annotation Router) must type of String");
						}
						paramRouterIndex = i;
						break findRouter;
					}
				}
			}
			
			if (paramRouterIndex == -1 && defaultRouter == null) {
				// 找不到方法的router(可以在方法中指定某个参数为Router或者在接口上设置默认的Router)
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") method(" + m.getName() + ") don't have Router, should declare on class or method's param");
			}
			
			MethodInfo info = new MethodInfo(reqType, cmd, timeout, defaultRouter, paramRouterIndex);
			if (methodNameMapInfo.containsKey(m.getName())) {
				// 代理接口中不允许有同名的方法
				throw new RuntimeException(
						"interface(" + interfaceClass.getName() + ") exits the same method name(" + m.getName() + "), is not allow");
			} else {
				methodNameMapInfo.put(m.getName(), info);
			}
		}
		cmdSet = null;
		
		try {
			serviceRegister.registerServiceListener(serviceName);
		} catch (Exception e) {
			Log.sessionLogger.error("serviceRegister.registerServiceListener Exception", e);
			throw new RuntimeException(e);
		}
		
		return clientStubFatory.createClientStub(interfaceClass.getName(), interfaceClass, methodNameMapInfo);
	}
	
}
